package com.azunitech.intercpt;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public class CircuitBreakerInterceptor implements MethodInterceptor  
{  
    private Map<String, CircuitBreaker> breakers = new HashMap<String, CircuitBreaker>();  
    private transient final Log log = LogFactory.getLog(getClass());  
    private List<Class<? extends Throwable>> nonTrippingExceptions;  
    private final int failureThreshold;  
    private final int timeUntilRetry;  
  
    public CircuitBreakerInterceptor(int failureThreshold, int timeUntilRetry)  
    {  
        this.failureThreshold = failureThreshold;  
        this.timeUntilRetry = timeUntilRetry;  
    }  
  
    public CircuitBreaker getBreaker(String name)  
    {  
        if (breakers.get(name) == null)  
        {  
            breakers.put(name, new CircuitBreaker(name, failureThreshold, timeUntilRetry));  
        }  
        return breakers.get(name);  
    }  
  
    private boolean isNonTrippingException ( Throwable t) {  
        for (Class throwable : nonTrippingExceptions)  
        {  
            if (throwable.isAssignableFrom(t.getClass()))  
            {  
                return true;  
            }  
        }  
        return false;  
    }  
  
    public List<Class<? extends Throwable>> getNonTrippingExceptions  
            ()  
    {  
        if (nonTrippingExceptions == null)  
            nonTrippingExceptions = new ArrayList<Class<? extends Throwable>>();  
        return nonTrippingExceptions;  
    }  
  
    public void setNonTrippingExceptions  
            (  
                    final List<Class<? extends Throwable>> nonTrippingExceptions)  
    {  
        this.nonTrippingExceptions = nonTrippingExceptions;  
    }  
  
    public Object invoke(final MethodInvocation invocation) throws Throwable  
    {  
        log.info("Going through circuitbreaker protected method");  
        UseCircuitBreaker breakerAnnot = invocation.getMethod().getAnnotation(UseCircuitBreaker.class);  
        if (breakerAnnot != null)  
        {  
            CircuitBreaker breaker = getBreaker(breakerAnnot.value());  
            Object returnValue = null;  
            if (breaker.isOpen())  
            {  
                if (breaker.isWaitTimeExceeded())  
                {  
                    try  
                    {  
                        breaker.openHalf();  
                        log.info("Retrying operation " + invocation.getMethod().toGenericString() + " after waitTime exceeded");  
                        returnValue = invocation.proceed();  
                        log.info("Retry of operation " + invocation.getMethod().toGenericString() + " succeeded, resetting circuit breaker");  
                        breaker.close();  
                    }  
                    catch (Throwable t)  
                    {  
                        if (isNonTrippingException(t))  
                        {  
                            log.info("Retry of operation " + invocation.getMethod().toGenericString() + " succeeded (but threw legal exception), resetting circuit breaker", t);  
                            breaker.close();  
                            throw t;  
                        }  
                        log.error("Retry of operation " + invocation.getMethod().toGenericString() + " failed, circuit breaker still closed.", t);  
                        breaker.open();  
                        throw new CircuitBreakerException("The operation " + invocation.getMethod().toGenericString() + " has too many failures, tripping circuit breaker.", t);  
                    }  
                }  
                else  
                {  
                    throw new CircuitBreakerException("This operation cannot be performed due to open circuit breaker (too many failures).");  
                }  
            }  
            else if (breaker.isClosed())  
            {  
                try  
                {  
                    returnValue = invocation.proceed();  
                    breaker.close();  
                }  
                catch (Throwable t)  
                {  
                    log.info("Failure of operation " + invocation.getMethod().toGenericString() + " in circuit breaker", t);  
                    if (!isNonTrippingException(t))  
                    {  
                        breaker.addFailure();  
                        if (breaker.isThresholdReached())  
                        {  
                            log.error("Circuit breaker tripped on operation " + invocation.getMethod().toGenericString() + " failure.", t);  
                            breaker.open();  
                            throw new CircuitBreakerException("The operation " + invocation.getMethod().toGenericString() + " has too many failures, tripping circuit breaker.", t);  
                        }  
                        else  
                        {  
                            throw t;  
                        }  
                    }  
                    else  
                    {  
                        throw t;  
                    }  
                }  
            }  
            else if (breaker.isHalfOpen())  
            {  
                throw new CircuitBreakerException("Busy retrying operation in opened circuit breaker");  
            }  
            return returnValue;  
        }  
        else  
        {  
            return invocation.proceed();  
        }  
    }  
}  
